package cn.lingyangwl.agile.auth.service.impl;

import cn.lingyangwl.agile.auth.assembly.ExtLdpConnectAssembly;
import cn.lingyangwl.agile.auth.manager.ExtLdpConnectManager;
import cn.lingyangwl.agile.auth.manager.OAuth2ClientManager;
import cn.lingyangwl.agile.auth.mapper.ExtLdpConnectMapper;
import cn.lingyangwl.agile.auth.model.entity.ExtLdpConnect;
import cn.lingyangwl.agile.auth.model.entity.OAuth2Client;
import cn.lingyangwl.agile.auth.model.rqrs.ldp.ExtLdpConnectBase;
import cn.lingyangwl.agile.auth.model.rqrs.ldp.ExtLdpConnectResp;
import cn.lingyangwl.agile.auth.model.rqrs.ldp.ExtLdpConnectSaveReq;
import cn.lingyangwl.agile.auth.model.rqrs.ldp.ExtLdpConnectUpdateReq;
import cn.lingyangwl.agile.auth.service.ExtLdpConnectService;
import cn.lingyangwl.agile.auth.service.OAuth2ClientService;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.auth.GrantTypeEnum;
import cn.lingyangwl.agile.tenant.core.Tenant;
import cn.lingyangwl.framework.data.mate.annotations.FieldBindHandle;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 身份源连接模板
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class ExtLdpConnectServiceImpl extends ServiceImpl<ExtLdpConnectMapper, ExtLdpConnect>
    implements IService<ExtLdpConnect>, ExtLdpConnectService {
    @Resource
    private ExtLdpConnectManager ldpConnectManager;
    @Resource
    private ExtLdpConnectAssembly ldpConnectAssembly;
    @Resource
    private OAuth2ClientManager clientManager;
    @Resource
    @Lazy
    private OAuth2ClientService clientService;

    @Override
    @FieldBindHandle
    @Tenant(level = DataIsolateLevel.MGT)
    public List<ExtLdpConnectResp> listLdpConnect(Long ldpId) {
        return ldpConnectManager.listByLdpId(ldpId).stream().map(ldpConnectAssembly::toConnectResp)
                .collect(Collectors.toList());
    }

    @Override
    public boolean saveLdpConnect(ExtLdpConnectSaveReq req) {
        ExtLdpConnect entity = ldpConnectAssembly.toConnect(req);
        checkLdpConnect(req, null);
        return this.save(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateLdpConnect(ExtLdpConnectUpdateReq req) {
        ExtLdpConnect entity = ldpConnectAssembly.toConnect(req);
        checkLdpConnect(req, req.getId());

        ExtLdpConnect oldLdpConnect = this.getById(req.getId());
        if (Objects.isNull(oldLdpConnect)) {
            throw new BizException("身份源连接不存在");
        }

        // 更新客户端的grantType
        List<OAuth2Client> clientList = clientManager.list();
        clientList.forEach(e -> {
            List<String> oldList = StringUtils.toList(e.getExtGrantTypes(), ",", String.class);
            // 如果当前更新的grant_type被更新, 则需要移除旧的
            oldList.remove(oldLdpConnect.getGrantType());
            e.setExtGrantTypes(String.join(",", oldList));

            boolean isContain = req.getAuthClientIds().contains(e.getId());
            if (isContain) {
                Set<String> newSet = new HashSet<>(oldList);
                newSet.add(req.getGrantType());
                e.setExtGrantTypes(String.join(",", newSet));
            }
        });

        this.clientManager.updateBatchById(clientList);
        return this.updateById(entity);
    }

    public void checkLdpConnect(ExtLdpConnectBase req, Long ldpConnectId) {
        LambdaQueryWrapper<ExtLdpConnect> lqw = ExtLdpConnect.lqw()
                .ne(Objects.nonNull(ldpConnectId), ExtLdpConnect::getId, ldpConnectId)
                .isNotNull(ExtLdpConnect::getGrantType)
                .eq(ExtLdpConnect::getGrantType, req.getGrantType());
        if (this.ldpConnectManager.count(lqw) > 0) {
            throw new BizException("授权方式 [{}] 已存在", req.getGrantType());
        }
    }

    @Override
    public boolean deleteLdpConnect(Long id) {
        ExtLdpConnect ldpConnect = this.getById(id);
        if (Objects.isNull(ldpConnect)) {
            return true;
        }

        List<OAuth2Client> clientDetails = clientManager.list();
        clientDetails.forEach(e -> {
            List<String> oldList = StringUtils.toList(e.getExtGrantTypes(), ",", String.class);
            oldList.remove(ldpConnect.getGrantType());
            e.setExtGrantTypes(String.join(",", oldList));
        });
        this.clientManager.updateBatchById(clientDetails);
        return this.removeById(id);
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public ExtLdpConnect getByGrantType(GrantTypeEnum grantType) {
        List<ExtLdpConnect> list = this.ldpConnectManager.listByGrantTypes(Collections.singletonList(grantType.getCode()));
        if (CollectionUtils.isEmpty(list)) {
            log.warn("grantType [{}] 尚未配置身份源", grantType);
            throw new BizException("提供商尚未配置身份源, 请联系管理员");
        }
        return list.get(0);
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public List<ExtLdpConnectResp> listConnectByClientId(String clientId) {
        // 通过id查找客户端
        OAuth2Client client = clientService.getByClientId(clientId);
        if (Objects.isNull(client)) {
            return Collections.emptyList();
        }

        // 通过 grant_type 查找 LdpConnect 数据
        List<String> grantTypes = StringUtils.toList(client.getExtGrantTypes(), ",", String.class);
        List<ExtLdpConnect> ldpConnectList = ldpConnectManager.listByGrantTypes(grantTypes);
        if (CollectionUtils.isEmpty(ldpConnectList)) {
            return Collections.emptyList();
        }
        
        return ldpConnectList.stream().map(ldpConnectAssembly::toConnectResp).collect(Collectors.toList());
    }
}
